package net.babelphish.altitudesig;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;

import javax.servlet.ServletContext;
import javax.servlet.http.*;

import java.text.DateFormat;
import java.text.SimpleDateFormat;

import com.googlecode.charts4j.*;

import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;

import com.google.appengine.api.datastore.Blob;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.images.Composite;
import com.google.appengine.api.images.Image;
import com.google.appengine.api.images.ImagesService;
import com.google.appengine.api.images.ImagesServiceFactory;
import com.google.appengine.api.images.Transform;
import com.google.appengine.api.images.Composite.Anchor;
import com.google.appengine.api.urlfetch.*;

import ar.com.hjg.pngj.*;

import org.apache.commons.lang3.*;

//import static com.googlecode.charts4j.UrlUtil.normalize;
enum GAME_TYPE {
	BALL,
	TBD
};

@SuppressWarnings("serial")
public class AltitudeSigServlet extends HttpServlet {
	
	final static int hoursUntilExpiration = 3;
	final static int signatureWidth = 440;
	final static int signatureHeight = 84;
	final static int maximumRatings = 46;
	
	public void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws IOException {
		
		String vaporID = req.getParameter("vaporid");
		String gameType = req.getParameter("type");
		String debug = req.getParameter("debug");
		boolean debugMode = false; 
		
		if ((debug != null) && debug.equals("true"))
		{
			debugMode = true;
		}
		
		GAME_TYPE requestedGameType = null;
		if (vaporID == null) {
			return;
		}
		if (gameType == null) {
			return;
		}
		else //figure out what game type we want
		{
			if (gameType.toLowerCase(Locale.ENGLISH).equals("ball")) {
				requestedGameType = GAME_TYPE.BALL;
			}
			else if (gameType.toLowerCase(Locale.ENGLISH).equals("tbd")) {
				requestedGameType = GAME_TYPE.TBD;
			}
			else //not a recognized game type
			{
				displayError("Game type not recognized:" + gameType, resp);	
				return;
			}
		}
		
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		Key imageKey = KeyFactory.createKey("SigImage", requestedGameType.toString() + vaporID);
		
		Entity storedImage;
		boolean loadedImage = false;
		byte[] finalImage = new byte[0];
		Date lastModifiedDate = new Date();
		Calendar cal = Calendar.getInstance();
		Date currentTime = cal.getTime();
		try {
			storedImage = datastore.get(imageKey);
			long ifModifiedHeader = req.getDateHeader("If-Modified-Since");
			lastModifiedDate = (Date)storedImage.getProperty("LastModified");
			if (ifModifiedHeader != -1) { //then they do have a cached copy
				Date ifModifiedSinceDate = new Date(ifModifiedHeader);

				if (!lastModifiedDate.after(ifModifiedSinceDate)) { //then we can safely peace out because their cache is up to date
					resp.setStatus(304);
					return;
				}
			}
			
			Blob finalImageBlob = (Blob)storedImage.getProperty("ImageData");
			
			cal.setTime(lastModifiedDate);
			cal.add(Calendar.HOUR, hoursUntilExpiration);
			if (currentTime.before(cal.getTime())) {
				finalImage = finalImageBlob.getBytes();
				loadedImage = true;
			}
		} catch (EntityNotFoundException e) {
			//nothing found, loadedImage should be false
		}
		
		if (!loadedImage || debugMode) { //we load the image and store in the datastore
			finalImage = compositeImage(vaporID, requestedGameType);						
			Blob imageBlob = new Blob(finalImage);
			Entity imageToStore = new Entity("SigImage", imageKey.getName());
			imageToStore.setProperty("ImageData", imageBlob);
			cal = Calendar.getInstance();
			lastModifiedDate = cal.getTime();
			lastModifiedDate = new Date((long) (Math.floor(lastModifiedDate.getTime() / 1000.0) * 1000));
			imageToStore.setProperty("LastModified", lastModifiedDate);
			datastore.put(imageToStore);
		}

		setLastModifiedDate(resp, lastModifiedDate);
		resp.setContentType("image/png");
		resp.getOutputStream().write(finalImage);
	}
	
	public static void setLastModifiedDate(HttpServletResponse response, Date lastModified) {
		if (response != null) {
			//response.setHeader("Cache-Control", "PUBLIC, max-age=" + seconds + ", must-revalidate");
			response.setHeader("Last-Modified", headerDateFormat().format(lastModified));
			Calendar cal = Calendar.getInstance();
			cal.setTime(lastModified);
			cal.add(Calendar.HOUR, hoursUntilExpiration);
			response.setHeader("Expires", headerDateFormat().format(cal.getTime()));
		}
	}

    private static DateFormat headerDateFormat() {
        DateFormat httpDateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
        httpDateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
        return httpDateFormat;
    }

		
	private byte[] compositeImage(String vaporID, GAME_TYPE requestedGameType) throws IOException
	{
		//get the data 
		DataScraper scraper = ScraperFactory.getDataScraper(SCRAPER_TYPE.PROFILE_JSON);
		scraper.scrapeData(vaporID, requestedGameType);
		
		ImagesService imagesService = ImagesServiceFactory.getImagesService();
		URLFetchService fetchService = URLFetchServiceFactory.getURLFetchService();

		HTTPResponse chartTextResponse = fetchService.fetch(generateChartTextURL("W/L: (" + scraper.getWinLoss() + ")", scraper.getRank(), scraper.getCurrentRating()));
		HTTPResponse chartResponse = fetchService.fetch(generateChartURL(scraper.getRatings()));
		byte[] usernameImageData = fetchService.fetch(generateUsernameURL(scraper.getName())).getContent();
		byte[] chartTextData = chartTextResponse.getContent();
		byte[] chartImageData = chartResponse.getContent();
        
		List<Composite> imagesToComposite = new ArrayList<Composite>();

		Image chartTextImage = ImagesServiceFactory.makeImage(chartTextData);
		Transform leftCrop = ImagesServiceFactory.makeCrop(0.01, 0, 1, 1);
		imagesService.applyTransform(leftCrop, chartTextImage);
		
		Image chartImage = ImagesServiceFactory.makeImage(chartImageData);
		imagesService.applyTransform(leftCrop, chartImage);
		
		Image badgeImage = ImagesServiceFactory.makeImage(getImageBytes("/WEB-INF/badges/" + scraper.getBadge() + ".png"));
		Transform resizeBadge = ImagesServiceFactory.makeResize(20, 20);
		Image smallBadgeImage = imagesService.applyTransform(resizeBadge, badgeImage);
		
		Image usernameImage = ImagesServiceFactory.makeImage(usernameImageData);
		double calculatedWidth = getImageTextWidth(usernameImage) + 10;
		Transform cornerCrop = ImagesServiceFactory.makeCrop(0.01, 0, (calculatedWidth/usernameImage.getWidth()), 0.8);
		imagesService.applyTransform(cornerCrop, usernameImage);
		double trueNameWidth = usernameImage.getWidth() + 30;
		double nameTagOffset = (signatureWidth / 2) - (trueNameWidth / 2);
		
		Image leftNameTag = ImagesServiceFactory.makeImage(getImageBytes("/WEB-INF/images/leftnametag.png"));
		Image rightNameTag = ImagesServiceFactory.makeImage(getImageBytes("/WEB-INF/images/rightnametag.png"));
		Transform nameTagCrop = ImagesServiceFactory.makeCrop(0, 0, (trueNameWidth/leftNameTag.getWidth()), 1);
		imagesService.applyTransform(nameTagCrop, leftNameTag);
		
		Image backgroundImage;
		if (requestedGameType == GAME_TYPE.BALL) {
			backgroundImage = ImagesServiceFactory.makeImage(getImageBytes("/WEB-INF/images/background-ball.png"));
		} else if (requestedGameType == GAME_TYPE.TBD){
			backgroundImage = ImagesServiceFactory.makeImage(getImageBytes("/WEB-INF/images/background-tbd.png"));
		} else {
			backgroundImage = ImagesServiceFactory.makeImage(getImageBytes("/WEB-INF/images/background.png"));
		}
        
        imagesToComposite.add(ImagesServiceFactory.makeComposite(backgroundImage, 0, 0, 1, Anchor.TOP_LEFT));
        imagesToComposite.add(ImagesServiceFactory.makeComposite(chartImage, -45, -15, 0.8f, Anchor.BOTTOM_RIGHT));
        imagesToComposite.add(ImagesServiceFactory.makeComposite(chartTextImage, 151, 27, 1, Anchor.TOP_LEFT));
        
        //draw nametag, badges and username
        double totalNameTagWidth = leftNameTag.getWidth() + rightNameTag.getWidth();
        imagesToComposite.add(ImagesServiceFactory.makeComposite(leftNameTag, (int)nameTagOffset, -1, 1, Anchor.TOP_LEFT));
        imagesToComposite.add(ImagesServiceFactory.makeComposite(rightNameTag, (int)nameTagOffset + leftNameTag.getWidth(), -1, 1, Anchor.TOP_LEFT));
        //add badges
        imagesToComposite.add(ImagesServiceFactory.makeComposite(smallBadgeImage, (int)nameTagOffset + 7, 3, 0.8f, Anchor.TOP_LEFT));
        imagesToComposite.add(ImagesServiceFactory.makeComposite(smallBadgeImage, (int)(nameTagOffset + totalNameTagWidth - (4 + smallBadgeImage.getWidth())), 3, 0.8f, Anchor.TOP_LEFT));
        //add name
        imagesToComposite.add(ImagesServiceFactory.makeComposite(usernameImage, (int)((nameTagOffset + (totalNameTagWidth / 2.0)) - (usernameImage.getWidth() / 2.0)) + 2, 5, 1, Anchor.TOP_LEFT));
        
		byte[] finalImage = ImagesServiceFactory.getImagesService().composite(imagesToComposite, signatureWidth, signatureHeight, 0).getImageData();
		
		return finalImage;
	}
			
	private static int getImageTextWidth(Image imageWithText)
	{
		PngReader pngr = new PngReader(new ByteArrayInputStream(imageWithText.getImageData()), "image");

        // this can copy some metadata from reader
        int channels = pngr.imgInfo.channels;
        
        ImageLine l1 = null;
        int row = 0;
        while (row < (pngr.imgInfo.rows / 2)) 
        {
        	l1 = pngr.readRow(row);
        	row++;
        }        
        
        int transparency = 0;
        int j = pngr.imgInfo.cols;
        while ((transparency < 5) && (j > 0)) 
        {
        	j--;
            transparency = l1.scanline[j * channels];
        }
        pngr.end();
        return j;
	}
	
	private void displayError(String error, HttpServletResponse resp) {
		try {
			resp.getOutputStream().print(error);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	private byte[] getImageBytes(String imagePath) throws IOException 
	{
		ServletContext context = getServletContext();
		InputStream is = context.getResourceAsStream(imagePath);
        
        final File file = new File(context.getRealPath(imagePath));
        long length = file.length();
        
        byte[] bytes = new byte[(int)length];

        // Read in the bytes
        int offset = 0;
        int numRead = 0;
        while (offset < bytes.length
               && (numRead=is.read(bytes, offset, bytes.length-offset)) >= 0) {
            offset += numRead;
        }

        // Ensure all the bytes have been read in
        if (offset < bytes.length) {
            throw new IOException("Could not completely read file "+file.getName());
        }

        // Close the input stream and return bytes
        is.close();
        return bytes;
	}
	
	private URL generateChartURL(ArrayList<Double> ratings)
	{
		//cull ratings list
		if (ratings.size() > maximumRatings) { //we want to reduce the number of ratings so it isn't so jagged
			ArrayList<Double> newRatingsSet = new ArrayList<Double>();
			double ratio = ((ratings.size()) / (double)maximumRatings);
			
			newRatingsSet.add(ratings.get(0));
			for(int i = 0; i < maximumRatings; i++) {
				Integer sparseRatingIndex = (int) (ratio * i); 
				if ((sparseRatingIndex > 0) && (sparseRatingIndex < (ratings.size() - 1)))
				{
					newRatingsSet.add(ratings.get(sparseRatingIndex));
				}
			}
			newRatingsSet.add(ratings.get(ratings.size() - 1));
			
			ratings = newRatingsSet;
		}
		Double smallestRating = Collections.min(ratings);
		Double largestRating = Collections.max(ratings);
		Data ratingsData = DataUtil.scaleWithinRange(smallestRating - 50, largestRating + 50, ratings);
		double[] scaledData = ratingsData.getData();
		double rangeLine = scaledData[0];
        Line line1 = Plots.newLine(Data.newData(scaledData), Color.WHITE);
        // Defining chart.
        
        LineChart chart = GCharts.newLineChart(line1);
        chart.addHorizontalRangeMarker(rangeLine-1, rangeLine + 1, Color.ORANGE);        
        chart.setSize(150, 40);
        chart.setBackgroundFill(Fills.newSolidFill(Color.newColor("000000", 100)));
        
        // Fetch the image at the location given by the url query string parameter
		URL url = null;
		try {
			url = new URL(chart.toURLString());
			URI uri = new URI(url.getProtocol(), url.getAuthority(), url.getPath(), url.getQuery(), url.getRef());
			url = uri.toURL();
			
		} catch (MalformedURLException e) {
		} catch (URISyntaxException e) {
		}
		return url;
	}
	
	private URL generateUsernameURL(String username)
	{
        LineChart chart = GCharts.newLineChart();
        chart.setSize(signatureWidth, 22);
        chart.setBackgroundFill(Fills.newSolidFill(Color.newColor("000000", 0))); //this is a transparent fill
        chart.setTransparency(100);
        
        try {
        	String encodedName = URLEncoder.encode(StringEscapeUtils.unescapeHtml4(username.replace("|", "I")), "ISO-8859-1");
			chart.addMarker(Markers.newTextMarker(encodedName, Color.WHITE, 16), 2, 82);			
		} catch (UnsupportedEncodingException e1) {
		}
		
		URL url = null;
		try {
			//fix for pipes which this picky URL parser doesn't like
			url = new URL(chart.toURLString().replace("|","%7C"));			
		} catch (Exception e) {
			e.printStackTrace();
		}
		return url;
	}
	
	private URL generateChartTextURL(String winLoss, Integer rank, Integer rating)
	{
        LineChart chart = GCharts.newLineChart();
        chart.setSize(signatureWidth, signatureHeight);
        chart.setBackgroundFill(Fills.newSolidFill(Color.newColor("000000", 0)));
        chart.setTransparency(100);

        try {
			chart.addMarker(Markers.newTextMarker(URLEncoder.encode("World Rank: " + Integer.toString(rank), "ISO-8859-1"), Color.WHITE, 12), 0, 85);
			chart.addMarker(Markers.newTextMarker(URLEncoder.encode(winLoss, "ISO-8859-1"), Color.WHITE, 12), 0, 68);
			chart.addMarker(Markers.newTextMarker(URLEncoder.encode("Rating: " + rating.toString(), "ISO-8859-1"), Color.WHITE, 12), 0, 51);
		} catch (UnsupportedEncodingException e1) {
			// TODO Auto-generated catch block
		}
        
        // Fetch the image at the location given by the url query string parameter
		URL url = null;
		try {
			url = new URL(chart.toURLString().replace("|","%7C"));			
		} catch (Exception e) {
			e.printStackTrace();
		}
		return url;
		
	}
}
